{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np # type: ignore\n",
    "from numba import njit # type: ignore\n",
    "import pandas as pd \n",
    "import pybroker\n",
    "from pybroker import Strategy\n",
    "#import talib  \n",
    "import matplotlib.pyplot as plt # type: ignore\n",
    "#import akshare as ak #提取上证50股票代码\n",
    "#from pybroker.ext.data import AKShare # type: ignore\n",
    "#pybroker.enable_data_source_cache('AKShare')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 股票数据和指标数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 读取csv\n",
    "df_sh = pd.read_csv('data/sh.csv') # 股票日数据\n",
    "df_bwwmacd = pd.read_csv('data/bwwmacd.csv') # 股票指标数据(大智慧导出)\n",
    "\n",
    "# 提取需要列\n",
    "selected_columns = ['日期', '开盘', '最高', '最低', '收盘', '成交量', 'MA5', 'MA10', 'MA30', 'MA60']\n",
    "df_sh = df_sh[selected_columns]\n",
    "# 日期列转换时间类型\n",
    "df_sh['日期'] = pd.to_datetime(df_sh['日期'])\n",
    "df_bwwmacd['日期'] = pd.to_datetime(df_bwwmacd['日期'])\n",
    "\n",
    "# 改变列名称\n",
    "df_sh.columns = ['date', 'open', 'close', 'high', 'low', 'volume','MA5','MA10','MA30',\t'MA60']\n",
    "df_bwwmacd.columns = ['date', 'M5', 'M10', 'M20']\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "# pybroker投喂数据格式\n",
    "df_sh = df_sh.iloc[:, :7]\n",
    "# 假添加的symbol  \n",
    "symbol = '000001'  \n",
    "# 创建一个新的'symbol'列，并填充为常量值  \n",
    "df_sh['symbol'] = symbol  \n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['date', 'open', 'close', 'high', 'low', 'volume', 'MA5']"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 获取除了'symbol'列之外的所有列名  \n",
    "original_cols = [col for col in df_sh.columns if col != 'symbol'] \n",
    "original_cols"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 重新排序列的顺序，将'symbol'列放在'date'列之后  \n",
    "new_cols = ['date', 'symbol'] + [col for col in original_cols if col != 'date']  \n",
    "# 使用新的列顺序重新排列DataFrame的列  \n",
    "df_sh = df_sh[new_cols]  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.merge(df_sh, df_bwwmacd, on='date', how='inner')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>date</th>\n",
       "      <th>symbol</th>\n",
       "      <th>open</th>\n",
       "      <th>close</th>\n",
       "      <th>high</th>\n",
       "      <th>low</th>\n",
       "      <th>volume</th>\n",
       "      <th>MA5</th>\n",
       "      <th>M5</th>\n",
       "      <th>M10</th>\n",
       "      <th>M20</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1990-12-19</td>\n",
       "      <td>000001</td>\n",
       "      <td>96.05</td>\n",
       "      <td>99.98</td>\n",
       "      <td>95.79</td>\n",
       "      <td>99.98</td>\n",
       "      <td>1260</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1990-12-20</td>\n",
       "      <td>000001</td>\n",
       "      <td>104.30</td>\n",
       "      <td>104.39</td>\n",
       "      <td>99.98</td>\n",
       "      <td>104.39</td>\n",
       "      <td>197</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1990-12-21</td>\n",
       "      <td>000001</td>\n",
       "      <td>109.07</td>\n",
       "      <td>109.13</td>\n",
       "      <td>103.73</td>\n",
       "      <td>109.13</td>\n",
       "      <td>28</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1990-12-24</td>\n",
       "      <td>000001</td>\n",
       "      <td>113.57</td>\n",
       "      <td>114.55</td>\n",
       "      <td>109.13</td>\n",
       "      <td>114.55</td>\n",
       "      <td>32</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1990-12-25</td>\n",
       "      <td>000001</td>\n",
       "      <td>120.09</td>\n",
       "      <td>120.25</td>\n",
       "      <td>114.55</td>\n",
       "      <td>120.25</td>\n",
       "      <td>15</td>\n",
       "      <td>109.660</td>\n",
       "      <td>0.8</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8168</th>\n",
       "      <td>2024-06-03</td>\n",
       "      <td>000001</td>\n",
       "      <td>3085.98</td>\n",
       "      <td>3097.20</td>\n",
       "      <td>3061.28</td>\n",
       "      <td>3078.49</td>\n",
       "      <td>357563360</td>\n",
       "      <td>3095.514</td>\n",
       "      <td>-1106.6</td>\n",
       "      <td>-664.6</td>\n",
       "      <td>70.40</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8169</th>\n",
       "      <td>2024-06-04</td>\n",
       "      <td>000001</td>\n",
       "      <td>3071.32</td>\n",
       "      <td>3095.32</td>\n",
       "      <td>3063.59</td>\n",
       "      <td>3091.20</td>\n",
       "      <td>309287584</td>\n",
       "      <td>3091.840</td>\n",
       "      <td>-1146.8</td>\n",
       "      <td>-825.3</td>\n",
       "      <td>-37.70</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8170</th>\n",
       "      <td>2024-06-05</td>\n",
       "      <td>000001</td>\n",
       "      <td>3086.05</td>\n",
       "      <td>3092.35</td>\n",
       "      <td>3064.74</td>\n",
       "      <td>3065.40</td>\n",
       "      <td>290542336</td>\n",
       "      <td>3082.716</td>\n",
       "      <td>-1186.8</td>\n",
       "      <td>-971.0</td>\n",
       "      <td>-146.75</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8171</th>\n",
       "      <td>2024-06-06</td>\n",
       "      <td>000001</td>\n",
       "      <td>3069.44</td>\n",
       "      <td>3077.67</td>\n",
       "      <td>3040.83</td>\n",
       "      <td>3048.79</td>\n",
       "      <td>369867104</td>\n",
       "      <td>3074.138</td>\n",
       "      <td>-1224.4</td>\n",
       "      <td>-1076.7</td>\n",
       "      <td>-262.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8172</th>\n",
       "      <td>2024-06-07</td>\n",
       "      <td>000001</td>\n",
       "      <td>3053.92</td>\n",
       "      <td>3065.02</td>\n",
       "      <td>3031.04</td>\n",
       "      <td>3051.28</td>\n",
       "      <td>316342496</td>\n",
       "      <td>3067.032</td>\n",
       "      <td>-1258.2</td>\n",
       "      <td>-1141.6</td>\n",
       "      <td>-377.75</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>8173 rows × 11 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "           date  symbol     open    close     high      low     volume  \\\n",
       "0    1990-12-19  000001    96.05    99.98    95.79    99.98       1260   \n",
       "1    1990-12-20  000001   104.30   104.39    99.98   104.39        197   \n",
       "2    1990-12-21  000001   109.07   109.13   103.73   109.13         28   \n",
       "3    1990-12-24  000001   113.57   114.55   109.13   114.55         32   \n",
       "4    1990-12-25  000001   120.09   120.25   114.55   120.25         15   \n",
       "...         ...     ...      ...      ...      ...      ...        ...   \n",
       "8168 2024-06-03  000001  3085.98  3097.20  3061.28  3078.49  357563360   \n",
       "8169 2024-06-04  000001  3071.32  3095.32  3063.59  3091.20  309287584   \n",
       "8170 2024-06-05  000001  3086.05  3092.35  3064.74  3065.40  290542336   \n",
       "8171 2024-06-06  000001  3069.44  3077.67  3040.83  3048.79  369867104   \n",
       "8172 2024-06-07  000001  3053.92  3065.02  3031.04  3051.28  316342496   \n",
       "\n",
       "           MA5      M5     M10     M20  \n",
       "0          NaN     NaN     NaN     NaN  \n",
       "1          NaN     NaN     NaN     NaN  \n",
       "2          NaN     NaN     NaN     NaN  \n",
       "3          NaN     NaN     NaN     NaN  \n",
       "4      109.660     0.8     NaN     NaN  \n",
       "...        ...     ...     ...     ...  \n",
       "8168  3095.514 -1106.6  -664.6   70.40  \n",
       "8169  3091.840 -1146.8  -825.3  -37.70  \n",
       "8170  3082.716 -1186.8  -971.0 -146.75  \n",
       "8171  3074.138 -1224.4 -1076.7 -262.00  \n",
       "8172  3067.032 -1258.2 -1141.6 -377.75  \n",
       "\n",
       "[8173 rows x 11 columns]"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 创建自定义均线函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "def ma(bar_data):\n",
    "    #@njit  # Enable Numba JIT.\n",
    "    def vec_ma(values1,values2):\n",
    "        # Initialize the result array.\n",
    "        n = len(values)\n",
    "        \n",
    "        data = np.full((n, 2), np.nan)  \n",
    "        # 将NumPy数组转换为pandas DataFrame，并添加列标签  \n",
    "        df = pd.DataFrame(data, columns=['SMA_short', 'SMA_long'])  \n",
    "\n",
    "        # 计算短期（例如5日）和长期（例如20日）均线  \n",
    "        df['SMA_short'] =values1 \n",
    "        df['SMA_long'] = values2 \n",
    "            # 初始化金叉和死叉的列  \n",
    "        df['Cross'] = 0  \n",
    "        out = np.array([np.nan for _ in range(n)])\n",
    "\n",
    "        # For all bars starting at lookback:\n",
    "        # 遍历DataFrame，判断金叉和死叉  \n",
    "        for i in range(1, len(df)):  \n",
    "            if df['SMA_short'].iloc[i] > df['SMA_long'].iloc[i] and df['SMA_short'].iloc[i-1] < df['SMA_long'].iloc[i-1]:  \n",
    "                #df.at[df.index[i], 'Cross'] = 2 \n",
    "                out[i] =1\n",
    "            if df['SMA_short'].iloc[i] < df['SMA_long'].iloc[i] and df['SMA_short'].iloc[i-1] > df['SMA_long'].iloc[i-1]:  \n",
    "                #df.at[df.index[i], 'Cross'] = 1  \n",
    "                out[i] = -1\n",
    "        return out\n",
    "    return vec_ma(bar_data.M5, bar_data.M10)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 把自定义函数注册到框架内部"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'指标函数中在 bar_data 之后的任何参数都将作为用户定义的参数传递给 pybroker.indicator。\\n一旦指标函数在 PyBroker 中注册，它将返回一个新的 Indicator 实例，该实例引用我们定义的指标函数。'"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 我们将指标函数命名为 cmma_20，并将 回溯 参数指定为 20 条。\n",
    "ma_520 = pybroker.indicator('ma_520', ma )\n",
    "'''指标函数中在 bar_data 之后的任何参数都将作为用户定义的参数传递给 pybroker.indicator。\n",
    "一旦指标函数在 PyBroker 中注册，它将返回一个新的 Indicator 实例，该实例引用我们定义的指标函数。'''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 创建买卖策略函数\n",
    "def buy_ma_cross(ctx):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "def buy_ma_cross(ctx):\n",
    "    if ctx.long_pos():\n",
    "        return\n",
    "    # Place a buy order if the most recent value of the 20 day CMMA is < 0:\n",
    "    # 这里调用了 ctx 对象的 indicator 方法，并传入了一个20日CMMA（自定义移动平均差异）指标的名字。\n",
    "    # [-1] 表示获取这个指标数组的最后一个值（即最新的值）。\n",
    "    # 如果这个值小于0，那么说明某种交易条件被触发了（可能是CMMA线向下穿越了零线）。\n",
    "    if ctx.indicator('ma_520')[-1] > 0:\n",
    "        #ctx.buy_shares = ctx.calc_target_shares(1000)\n",
    "        ctx.buy_shares=100 #金叉买\n",
    "    if ctx.indicator('ma_520')[-1] < 0:    \n",
    "        #ctx.hold_bars = 5 # 表示策略计划持有这些股票3个交易日\n",
    "        ctx.sell_shares = 100 #死叉全卖"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 运行回测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Backtesting: 2023-04-01 00:00:00 to 2024-06-07 00:00:00\n",
      "\n",
      "Computing indicators...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  0% (0 of 1) |                          | Elapsed Time: 0:00:00 ETA:  --:--:--\n"
     ]
    },
    {
     "ename": "AttributeError",
     "evalue": "Attribute 'M5' not found.",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[45], line 5\u001b[0m\n\u001b[0;32m      2\u001b[0m strategy\u001b[38;5;241m.\u001b[39madd_execution(buy_ma_cross,  [\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m000001\u001b[39m\u001b[38;5;124m'\u001b[39m], indicators\u001b[38;5;241m=\u001b[39mma_520)\n\u001b[0;32m      3\u001b[0m pybroker\u001b[38;5;241m.\u001b[39menable_indicator_cache(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmy_indicators\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m----> 5\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[43mstrategy\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbacktest\u001b[49m\u001b[43m(\u001b[49m\u001b[43mwarmup\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m20\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m#warmup 参数指定在运行回测执行之前需要经过 20 个 Bar：\u001b[39;00m\n\u001b[0;32m      6\u001b[0m \u001b[38;5;66;03m# 在Python的pandas库中，DataFrame 对象有一个方法 round()，\u001b[39;00m\n\u001b[0;32m      7\u001b[0m \u001b[38;5;66;03m# 该方法用于将DataFrame中的所有数值型列（通常是整数或浮点数）四舍五入到指定的小数位数。\u001b[39;00m\n\u001b[0;32m      8\u001b[0m result\u001b[38;5;241m.\u001b[39mmetrics_df\u001b[38;5;241m.\u001b[39mround(\u001b[38;5;241m4\u001b[39m)\n",
      "File \u001b[1;32mc:\\ProgramData\\Anaconda3\\envs\\pybroker\\Lib\\site-packages\\pybroker\\strategy.py:1087\u001b[0m, in \u001b[0;36mStrategy.backtest\u001b[1;34m(self, start_date, end_date, timeframe, between_time, days, lookahead, train_size, shuffle, calc_bootstrap, disable_parallel, warmup, portfolio)\u001b[0m\n\u001b[0;32m   1018\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mbacktest\u001b[39m(\n\u001b[0;32m   1019\u001b[0m     \u001b[38;5;28mself\u001b[39m,\n\u001b[0;32m   1020\u001b[0m     start_date: Optional[Union[\u001b[38;5;28mstr\u001b[39m, datetime]] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m   1031\u001b[0m     portfolio: Optional[Portfolio] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[0;32m   1032\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m TestResult:\n\u001b[0;32m   1033\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m\"\"\"Backtests the trading strategy by running executions that were added\u001b[39;00m\n\u001b[0;32m   1034\u001b[0m \u001b[38;5;124;03m    with :meth:`.add_execution`.\u001b[39;00m\n\u001b[0;32m   1035\u001b[0m \n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m   1085\u001b[0m \u001b[38;5;124;03m        history, and evaluation metrics.\u001b[39;00m\n\u001b[0;32m   1086\u001b[0m \u001b[38;5;124;03m    \"\"\"\u001b[39;00m\n\u001b[1;32m-> 1087\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mwalkforward\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   1088\u001b[0m \u001b[43m        \u001b[49m\u001b[43mwindows\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1089\u001b[0m \u001b[43m        \u001b[49m\u001b[43mlookahead\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mlookahead\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1090\u001b[0m \u001b[43m        \u001b[49m\u001b[43mstart_date\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstart_date\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1091\u001b[0m \u001b[43m        \u001b[49m\u001b[43mend_date\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mend_date\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1092\u001b[0m \u001b[43m        \u001b[49m\u001b[43mtimeframe\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeframe\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1093\u001b[0m \u001b[43m        \u001b[49m\u001b[43mbetween_time\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbetween_time\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1094\u001b[0m \u001b[43m        \u001b[49m\u001b[43mdays\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdays\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1095\u001b[0m \u001b[43m        \u001b[49m\u001b[43mtrain_size\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtrain_size\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1096\u001b[0m \u001b[43m        \u001b[49m\u001b[43mshuffle\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mshuffle\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1097\u001b[0m \u001b[43m        \u001b[49m\u001b[43mcalc_bootstrap\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcalc_bootstrap\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1098\u001b[0m \u001b[43m        \u001b[49m\u001b[43mdisable_parallel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdisable_parallel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1099\u001b[0m \u001b[43m        \u001b[49m\u001b[43mwarmup\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mwarmup\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1100\u001b[0m \u001b[43m        \u001b[49m\u001b[43mportfolio\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mportfolio\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1101\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mc:\\ProgramData\\Anaconda3\\envs\\pybroker\\Lib\\site-packages\\pybroker\\strategy.py:1217\u001b[0m, in \u001b[0;36mStrategy.walkforward\u001b[1;34m(self, windows, lookahead, start_date, end_date, timeframe, between_time, days, train_size, shuffle, calc_bootstrap, disable_parallel, warmup, portfolio)\u001b[0m\n\u001b[0;32m   1209\u001b[0m df \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_filter_dates(\n\u001b[0;32m   1210\u001b[0m     df\u001b[38;5;241m=\u001b[39mdf,\n\u001b[0;32m   1211\u001b[0m     start_date\u001b[38;5;241m=\u001b[39mstart_dt,\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m   1214\u001b[0m     days\u001b[38;5;241m=\u001b[39mday_ids,\n\u001b[0;32m   1215\u001b[0m )\n\u001b[0;32m   1216\u001b[0m tf_seconds \u001b[38;5;241m=\u001b[39m to_seconds(timeframe)\n\u001b[1;32m-> 1217\u001b[0m indicator_data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_fetch_indicators\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   1218\u001b[0m \u001b[43m    \u001b[49m\u001b[43mdf\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdf\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1219\u001b[0m \u001b[43m    \u001b[49m\u001b[43mcache_date_fields\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mCacheDateFields\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   1220\u001b[0m \u001b[43m        \u001b[49m\u001b[43mstart_date\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstart_dt\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1221\u001b[0m \u001b[43m        \u001b[49m\u001b[43mend_date\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mend_dt\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1222\u001b[0m \u001b[43m        \u001b[49m\u001b[43mtf_seconds\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtf_seconds\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1223\u001b[0m \u001b[43m        \u001b[49m\u001b[43mbetween_time\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbetween_time\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1224\u001b[0m \u001b[43m        \u001b[49m\u001b[43mdays\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mday_ids\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1225\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1226\u001b[0m \u001b[43m    \u001b[49m\u001b[43mdisable_parallel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdisable_parallel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1227\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1228\u001b[0m train_only \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m   1229\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_before_exec_fn \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m   1230\u001b[0m     \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_after_exec_fn \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m   1231\u001b[0m     \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mall\u001b[39m(\u001b[38;5;28mmap\u001b[39m(\u001b[38;5;28;01mlambda\u001b[39;00m e: e\u001b[38;5;241m.\u001b[39mfn \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_executions))\n\u001b[0;32m   1232\u001b[0m )\n\u001b[0;32m   1233\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m portfolio \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n",
      "File \u001b[1;32mc:\\ProgramData\\Anaconda3\\envs\\pybroker\\Lib\\site-packages\\pybroker\\strategy.py:1426\u001b[0m, in \u001b[0;36mStrategy._fetch_indicators\u001b[1;34m(self, df, cache_date_fields, disable_parallel)\u001b[0m\n\u001b[0;32m   1424\u001b[0m         \u001b[38;5;28;01mfor\u001b[39;00m ind_name \u001b[38;5;129;01min\u001b[39;00m execution\u001b[38;5;241m.\u001b[39mindicator_names:\n\u001b[0;32m   1425\u001b[0m             indicator_syms\u001b[38;5;241m.\u001b[39madd(IndicatorSymbol(ind_name, sym))\n\u001b[1;32m-> 1426\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcompute_indicators\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   1427\u001b[0m \u001b[43m    \u001b[49m\u001b[43mdf\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdf\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1428\u001b[0m \u001b[43m    \u001b[49m\u001b[43mindicator_syms\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindicator_syms\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1429\u001b[0m \u001b[43m    \u001b[49m\u001b[43mcache_date_fields\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcache_date_fields\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1430\u001b[0m \u001b[43m    \u001b[49m\u001b[43mdisable_parallel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdisable_parallel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1431\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mc:\\ProgramData\\Anaconda3\\envs\\pybroker\\Lib\\site-packages\\pybroker\\indicator.py:222\u001b[0m, in \u001b[0;36mIndicatorsMixin.compute_indicators\u001b[1;34m(self, df, indicator_syms, cache_date_fields, disable_parallel)\u001b[0m\n\u001b[0;32m    219\u001b[0m             \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[0;32m    220\u001b[0m         sym_data[sym][col] \u001b[38;5;241m=\u001b[39m data[col]\u001b[38;5;241m.\u001b[39mto_numpy()\n\u001b[0;32m    221\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, (ind_sym, series) \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\n\u001b[1;32m--> 222\u001b[0m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_run_indicators\u001b[49m\u001b[43m(\u001b[49m\u001b[43msym_data\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muncached_ind_syms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdisable_parallel\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    223\u001b[0m ):\n\u001b[0;32m    224\u001b[0m     indicator_data[ind_sym] \u001b[38;5;241m=\u001b[39m series\n\u001b[0;32m    225\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_set_cached_indicator(series, ind_sym, cache_date_fields)\n",
      "File \u001b[1;32mc:\\ProgramData\\Anaconda3\\envs\\pybroker\\Lib\\site-packages\\pybroker\\indicator.py:300\u001b[0m, in \u001b[0;36mIndicatorsMixin._run_indicators\u001b[1;34m(self, sym_data, ind_syms, disable_parallel)\u001b[0m\n\u001b[0;32m    298\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m disable_parallel \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(ind_syms) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[0;32m    299\u001b[0m     scope\u001b[38;5;241m.\u001b[39mlogger\u001b[38;5;241m.\u001b[39mdebug_compute_indicators(is_parallel\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[1;32m--> 300\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mtuple\u001b[39;49m\u001b[43m(\u001b[49m\n\u001b[0;32m    301\u001b[0m \u001b[43m        \u001b[49m\u001b[43mfns\u001b[49m\u001b[43m[\u001b[49m\u001b[43mind_name\u001b[49m\u001b[43m]\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs_fn\u001b[49m\u001b[43m(\u001b[49m\u001b[43mind_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msym\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    302\u001b[0m \u001b[43m        \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mind_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msym\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mind_syms\u001b[49m\n\u001b[0;32m    303\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    304\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m    305\u001b[0m     scope\u001b[38;5;241m.\u001b[39mlogger\u001b[38;5;241m.\u001b[39mdebug_compute_indicators(is_parallel\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n",
      "File \u001b[1;32mc:\\ProgramData\\Anaconda3\\envs\\pybroker\\Lib\\site-packages\\pybroker\\indicator.py:301\u001b[0m, in \u001b[0;36m<genexpr>\u001b[1;34m(.0)\u001b[0m\n\u001b[0;32m    298\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m disable_parallel \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(ind_syms) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[0;32m    299\u001b[0m     scope\u001b[38;5;241m.\u001b[39mlogger\u001b[38;5;241m.\u001b[39mdebug_compute_indicators(is_parallel\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[0;32m    300\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mtuple\u001b[39m(\n\u001b[1;32m--> 301\u001b[0m         \u001b[43mfns\u001b[49m\u001b[43m[\u001b[49m\u001b[43mind_name\u001b[49m\u001b[43m]\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs_fn\u001b[49m\u001b[43m(\u001b[49m\u001b[43mind_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msym\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    302\u001b[0m         \u001b[38;5;28;01mfor\u001b[39;00m ind_name, sym \u001b[38;5;129;01min\u001b[39;00m ind_syms\n\u001b[0;32m    303\u001b[0m     )\n\u001b[0;32m    304\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m    305\u001b[0m     scope\u001b[38;5;241m.\u001b[39mlogger\u001b[38;5;241m.\u001b[39mdebug_compute_indicators(is_parallel\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n",
      "File \u001b[1;32mc:\\ProgramData\\Anaconda3\\envs\\pybroker\\Lib\\site-packages\\pybroker\\indicator.py:162\u001b[0m, in \u001b[0;36m_decorate_indicator_fn.<locals>.decorated_indicator_fn\u001b[1;34m(symbol, ind_name, date, open, high, low, close, volume, vwap, custom_col_data)\u001b[0m\n\u001b[0;32m    140\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mdecorated_indicator_fn\u001b[39m(\n\u001b[0;32m    141\u001b[0m     symbol: \u001b[38;5;28mstr\u001b[39m,\n\u001b[0;32m    142\u001b[0m     ind_name: \u001b[38;5;28mstr\u001b[39m,\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m    150\u001b[0m     custom_col_data: Mapping[\u001b[38;5;28mstr\u001b[39m, Optional[NDArray]],\n\u001b[0;32m    151\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28mtuple\u001b[39m[IndicatorSymbol, pd\u001b[38;5;241m.\u001b[39mSeries]:\n\u001b[0;32m    152\u001b[0m     bar_data \u001b[38;5;241m=\u001b[39m BarData(\n\u001b[0;32m    153\u001b[0m         date\u001b[38;5;241m=\u001b[39mdate,\n\u001b[0;32m    154\u001b[0m         \u001b[38;5;28mopen\u001b[39m\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mopen\u001b[39m,\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m    160\u001b[0m         \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mcustom_col_data,\n\u001b[0;32m    161\u001b[0m     )\n\u001b[1;32m--> 162\u001b[0m     series \u001b[38;5;241m=\u001b[39m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbar_data\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    163\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m IndicatorSymbol(ind_name, symbol), series\n",
      "File \u001b[1;32mc:\\ProgramData\\Anaconda3\\envs\\pybroker\\Lib\\site-packages\\pybroker\\indicator.py:100\u001b[0m, in \u001b[0;36mIndicator.__call__\u001b[1;34m(self, data)\u001b[0m\n\u001b[0;32m     98\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(data, pd\u001b[38;5;241m.\u001b[39mDataFrame):\n\u001b[0;32m     99\u001b[0m     data \u001b[38;5;241m=\u001b[39m _to_bar_data(data)\n\u001b[1;32m--> 100\u001b[0m values \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_fn\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    101\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(values, pd\u001b[38;5;241m.\u001b[39mSeries):\n\u001b[0;32m    102\u001b[0m     values \u001b[38;5;241m=\u001b[39m values\u001b[38;5;241m.\u001b[39mto_numpy()\n",
      "Cell \u001b[1;32mIn[42], line 28\u001b[0m, in \u001b[0;36mma\u001b[1;34m(bar_data)\u001b[0m\n\u001b[0;32m     26\u001b[0m             out[i] \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m\n\u001b[0;32m     27\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m out\n\u001b[1;32m---> 28\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m vec_ma(\u001b[43mbar_data\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mM5\u001b[49m, bar_data\u001b[38;5;241m.\u001b[39mM10)\n",
      "File \u001b[1;32mc:\\ProgramData\\Anaconda3\\envs\\pybroker\\Lib\\site-packages\\pybroker\\common.py:216\u001b[0m, in \u001b[0;36mBarData.__getattr__\u001b[1;34m(self, attr)\u001b[0m\n\u001b[0;32m    214\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_custom_col_data \u001b[38;5;129;01mand\u001b[39;00m attr \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_custom_col_data:\n\u001b[0;32m    215\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_custom_col_data[attr]\n\u001b[1;32m--> 216\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAttribute \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mattr\u001b[38;5;132;01m!r}\u001b[39;00m\u001b[38;5;124m not found.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
      "\u001b[1;31mAttributeError\u001b[0m: Attribute 'M5' not found."
     ]
    }
   ],
   "source": [
    "strategy = Strategy(df, '4/1/2023', '6/7/2024')\n",
    "strategy.add_execution(buy_ma_cross,  ['000001'], indicators=ma_520)\n",
    "pybroker.enable_indicator_cache('my_indicators')\n",
    "\n",
    "result = strategy.backtest(warmup=20) #warmup 参数指定在运行回测执行之前需要经过 20 个 Bar：\n",
    "# 在Python的pandas库中，DataFrame 对象有一个方法 round()，\n",
    "# 该方法用于将DataFrame中的所有数值型列（通常是整数或浮点数）四舍五入到指定的小数位数。\n",
    "result.metrics_df.round(4)\n",
    "print(result.metrics_df.round(4))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
